package run.microservice.stock.utils;

import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.condition.PathPatternsRequestCondition;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.util.pattern.PathPattern;
import run.microservice.stock.model.dto.ApiInfoDTO;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author lunfangyu
 */
@Slf4j
public class RequestMappingUtil {

    /**
     * 获取所有接口信息; 包括接口路径、请求方式、所在类名、方法、接口描述
     * @return
     */
    public static List<ApiInfoDTO> getAllApiInfo() {
        List<ApiInfoDTO> apiInfoList = new ArrayList<>();

        // 获取springmvc处理器映射器组件对象 RequestMappingHandlerMapping 无法直接注入
        // 要按名称查找，按类型会报错找到两个Bean: expected single matching bean but found 2: requestMappingHandlerMapping,controllerEndpointHandlerMapping
        RequestMappingHandlerMapping handlerMapping = SpringContextUtil.getBean("requestMappingHandlerMapping", RequestMappingHandlerMapping.class);
        if (handlerMapping == null) {
            return apiInfoList;
        }

        //获取url与类和方法的对应信息
        Map<RequestMappingInfo, HandlerMethod> handlerMethods = handlerMapping.getHandlerMethods();
        if (CollectionUtils.isEmpty(handlerMethods)) {
            return apiInfoList;
        }

        for (Map.Entry<RequestMappingInfo, HandlerMethod> methodEntry : handlerMethods.entrySet()) {
            ApiInfoDTO apiInfo = new ApiInfoDTO();

            RequestMappingInfo mappingInfo = methodEntry.getKey();
            HandlerMethod handlerMethod = methodEntry.getValue();

            // 反射获取url对应类名和方法名
            apiInfo.setMethodName(handlerMethod.getMethod().getName());
            apiInfo.setClassName(handlerMethod.getMethod().getDeclaringClass().getName());

            /*
            * 获取URL
            * 和路径匹配模式有关
            * 在springboot 2.6.0 之前，默认是ant_path_matcher解析方式。
            * 在2.6.0 之后，默认是path_pattern_parser解析方式
            *
            * ant_path_matcher使用PatternsRequestCondition
            * path_pattern_parser是使用PathPatternsRequestCondition
            * 所以两个都要判断
            */
            PatternsRequestCondition patternsCondition = mappingInfo.getPatternsCondition();
            if (patternsCondition != null) {
                for (String pattern : patternsCondition.getPatterns()) {
                    apiInfo.setApiPath(pattern);
                }
            }
            PathPatternsRequestCondition pathPatternsCondition = mappingInfo.getPathPatternsCondition();
            if (pathPatternsCondition != null) {
                for (PathPattern pattern : pathPatternsCondition.getPatterns()) {
                    apiInfo.setApiPath(pattern.getPatternString());
                }
            }

            // 获取请求类型
            RequestMethodsRequestCondition methodsCondition = mappingInfo.getMethodsCondition();
            for (RequestMethod requestMethod : methodsCondition.getMethods()) {
                apiInfo.setRequestType(requestMethod.name());
            }
            apiInfoList.add(apiInfo);
        }

        // 对结果进行排序
        return apiInfoList.stream().sorted(
                // 按接口路径正序排列
                Comparator.comparing(ApiInfoDTO::getApiPath)
                        // 然后按类名排列
                        .thenComparing(ApiInfoDTO::getClassName)
                        // 最后按方法名倒序排列
                        .thenComparing(ApiInfoDTO::getMethodName, Comparator.reverseOrder())
        ).collect(Collectors.toList());
    }
}
